package com.google.code.speedturtle.core.file;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.code.speedturtle.exception.InvalidFileBlockInfoException;

public class FileManager
{
	private static final Map<File, FileManager> managers =
			new HashMap<File, FileManager>();

	private final File targetFile;

	private final FileOutputStream opStream;
	
	private final List<FileBlock> blocks = new LinkedList<FileBlock>();

	private long writtenLength = 0;

	private FileManager(File targetFile) throws FileNotFoundException
	{
		this.targetFile = targetFile;
		this.opStream = new FileOutputStream(this.targetFile);
	}

	public static FileManager getManager(File targetFile)
			throws FileNotFoundException
	{
		FileManager manager = FileManager.managers.get(targetFile);

		if (manager != null)
			return manager;

		manager = new FileManager(targetFile);
		FileManager.managers.put(targetFile, manager);

		return manager;
	}

	public File getFile()
	{
		return this.targetFile;
	}

	public synchronized void write(long fileSkip,
			byte [] data, int offset, int length)
			throws IOException, InvalidFileBlockInfoException
	{
		FileBlock block;

		if (fileSkip == this.writtenLength) {
			this.opStream.write(data, offset, length);

			block = this.blocks.remove(0);
			this.write(block.fileSkip, block.data, block.offset, block.length);
			return;
		}

		block = new FileBlock(fileSkip, data, offset, length);
		this.addBlock(block);
	}

	private void addBlock(FileBlock block) throws InvalidFileBlockInfoException
	{
		int length = this.blocks.size();
		for (int i = 0; i < length; i++)
		{
			FileBlock storedBlock = this.blocks.get(i);

			if (storedBlock.fileSkip > block.fileSkip) {

				if ((block.fileSkip + block.length) > storedBlock.fileSkip)
					throw new InvalidFileBlockInfoException();

				this.blocks.add(i, block);
				return;
			}

			if (storedBlock.fileSkip == block.fileSkip)
				throw new InvalidFileBlockInfoException();
		}
	}
	
	public synchronized void flush() throws IOException
	{
		this.opStream.flush();
	}

	public synchronized void close() throws IOException
	{
		if (this.opStream != null)
			this.opStream.close();
	}
	
	private class FileBlock
	{
		private final long fileSkip;

		private final int offset;

		private final int length;

		private final byte [] data;

		private FileBlock(long fileSkip, byte [] data, int offset, int length)
		{
			this.fileSkip = fileSkip;
			this.data = data;
			this.offset = offset;
			this.length = length;
		}
	}
}